import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.XMLReader;

import java.sql.Timestamp;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;
import java.io.File;
import java.sql.Time;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import org.apache.commons.lang.StringEscapeUtils;

//change body class
class AdvChangeBody
{
	//elements
	private String project = null;
	private String commitHash = null;
	private String line = null;
	private String committerDate = null;
	
	//method to set elements
	public void setElement(int id, String content)
	{
		switch(id)
		{
			case AdvChangeReader.PROJECT:
			{
				project = content;
				
				break;
			}
			case AdvChangeReader.COMMITHASH:
			{
				commitHash = content;
				
				break;
			}
			case AdvChangeReader.LINE:
			{
				line = content;
				
				break;
			}
			case AdvChangeReader.COMMITTERDATE:
			{
				committerDate = content;
				
				break;
			}

			default:
			{
				break;
			}
		}
	}
	
	//method to get elements
	public String getElement(int id)
	{
		switch(id)
		{
			case AdvChangeReader.PROJECT:
			{
				return project;
				
			}
			case AdvChangeReader.LINE:
			{
				return line;
			}
			case AdvChangeReader.COMMITHASH:
			{
				return commitHash;
			}
			case AdvChangeReader.COMMITTERDATE:
			{
				return committerDate;
			}
	
			default:
			{
				break;
			}
		}
		
		return null;
	}
	
	public String toString()
	{
		String str = new String();
		
		str += ("proj: " + project + "\n");
		str += ("commit hash: " + commitHash + "\n");
		str += ("target: " + line + "\n");
		str += ("committer date: " + committerDate + "\n");
		
		
		return str;
	}
}


//a list for change body
class AdvChangeBodyList
{
	PreparedStatement acbps = null;
	
	private int count = 0;
	
	public AdvChangeBodyList(PreparedStatement acbps)
	{
		this.acbps = acbps;
	}
	
	private Timestamp toTimestamp(String timeStream)
	{
		if (timeStream.length() == 29 || timeStream.length() == 30)
		{
			//for annoying length difference caused by '4' and '11'
			int offset = 0;
			if (timeStream.length() == 29)
			{
				offset = -1;
			}
			
			//the day
			String day = new String(timeStream.substring(8, 10 + offset));
			
			//convert into numerical month
			int month = -1;
			char firstLetter = Character.toUpperCase(timeStream.charAt(4));
			switch(firstLetter)
			{
				case 'J':
				{
					if (Character.toUpperCase(timeStream.charAt(5)) == 'A')
					{
						month = 0;
					}
						else if (Character.toUpperCase(timeStream.charAt(6)) == 'N')
						{
							month = 5;
						}
						else
						{
							month = 6;
						}
					
					break;
				}
				case 'F':
				{
					month = 1;
					
					break;
				}
				case 'M':
				{
					if (Character.toUpperCase(timeStream.charAt(6)) == 'R')
					{
						month = 2;
					}
					else
					{
						month = 4;
					}
					
					break;
				}
				case 'A':
				{
					if (Character.toUpperCase(timeStream.charAt(5)) == 'P')
					{
						month = 3;
					}
					else
					{
						month = 7;
					}
					
					break;
				}
				case 'S':
				{
					month = 8;
					
					break;
				}
				case 'O':
				{
					month = 9;
					
					break;
				}
				case 'N':
				{
					month = 10;
					
					break;
				}
				case 'D':
				{
					month = 11;
					
					break;
				}
				default:
				{
					break;
				}
			}
			
			//year
			String year = new String(timeStream.substring(20 + offset, 24 + offset));
			
			//hour
			String hour = new String(timeStream.substring(11 + offset, 13 + offset));
			
			//minute
			String min = new String(timeStream.substring(14 + offset, 16 + offset));
			
			//second
			String sec = new String(timeStream.substring(17 + offset, 19 + offset));
		
			//sign
			int sign = timeStream.charAt(25 + offset) == '+' ? 1 : -1;
			
			//offset
			String hourOffset = new String(timeStream.substring(26 + offset, 
					28 + offset));
			
			String minOffset = new String(timeStream.substring(28 + offset, 
					30 + offset));
	
			
			int timeOffset = sign * (Integer.parseInt(hourOffset) * 3600000 
					+ Integer.parseInt(minOffset) * 60000);
		
			
			//set up a timezone
			SimpleTimeZone tZone = new SimpleTimeZone(timeOffset, "");
			
			Calendar dayTime = Calendar.getInstance();
			
			dayTime.setTimeZone(tZone);
			
			//set up the time
			dayTime.set(Integer.parseInt(year), month, Integer.parseInt(day),
					Integer.parseInt(hour), Integer.parseInt(min), Integer.parseInt(sec));
	
			/*
			System.out.println("stamp--> " + timeStream);
			System.out.println("hour offset --> " + hourOffset);
			System.out.println("min offset --> " + minOffset);
			System.out.println(dayTime.getTime().toString());
			*/
	
			return new Timestamp(dayTime.getTime().getTime());
			
			/*
			Timestamp tt = new Timestamp(dayTime.get(Calendar.YEAR) - 1900,
					dayTime.get(Calendar.MONTH), dayTime.get(Calendar.DAY_OF_MONTH),
					dayTime.get(Calendar.HOUR), dayTime.get(Calendar.MINUTE),
					dayTime.get(Calendar.SECOND), 0);
			//System.println();
			
			return tt;
			*/
		}
		
		System.err.print("time format--> " + timeStream);
		
		return null;
	}
	
	//add change body into this list
	//will push into db when necessary
	public void add(AdvChangeBody acb) throws NumberFormatException, SQLException
	{	
		count++;
		
		//add project
		String project = acb.getElement(AdvChangeReader.PROJECT);

		if (project == null)
		{
			acbps.setNull(1, Types.VARCHAR);
		}
		else
		{
			acbps.setString(1, project);
		}
		
		//add commit_hash
		String commitHash = acb.getElement(AdvChangeReader.COMMITHASH);
		if (commitHash == null)
		{
			acbps.setNull(2, Types.VARCHAR);
		}
		else
		{
			acbps.setString(2, commitHash);
		}
		

		
	
		
	
		//add committer_date
		String committerDate = acb.getElement(AdvChangeReader.COMMITTERDATE);
		if (committerDate == null)
		{
			acbps.setNull(3, Types.TIMESTAMP);
		}
		else
		{
			acbps.setTimestamp(3, toTimestamp(committerDate));
			//cbps.setString(10, committerDate);
		}
		
		//add subject
		String line = acb.getElement(AdvChangeReader.LINE);
		if (line == null)
		{
			acbps.setNull(4, Types.VARCHAR);
		}
		else
		{
			acbps.setString(4, line);
		}
		
		//add to batch
		acbps.addBatch();
		
		if (count > ChangeReader.CHANGEBODY_BATCH_SIZE)
		{
			count = 0;
			
			outsource();
		}
	}
	
	//push into db
	public void outsource() throws SQLException
	{
		acbps.executeBatch();
	}
	
}



public class AdvChangeReader
{
	//for xml filepath
	String filename;
	
	//element labels
	final public static int CHANGE = 0;
	
	//xml reader, used to read in the xml file
	private XMLReader xr = null;
	
	final public static int PROJECT = 1;
	final public static int COMMITHASH = 2;
	final public static int TREEHASH = 3;
	final public static int PARENTHASHES = 4;
	final public static int AUTHORNAME = 5;
	final public static int AUTHOREMAIL = 6;
	final public static int AUTHORDATE = 7;
	final public static int COMMITTERNAME = 8;
	final public static int COMMITTEREMAIL = 9;
	final public static int COMMITTERDATE = 10;
	final public static int SUBJECT = 11;
	
	final public static int LINE = 12;
	
	final public static int MESSAGE = 13;
	
	final public static int TARGET = 14;
	
	//database object
	static DB db = null;
	
	//batch size
	final public static int CHANGEBODY_BATCH_SIZE = 50;
	final public static int MULTIPLELINES_BATCH_SIZE = 100;
	
	//prepared statement
	private PreparedStatement acbps = null;
	
	//list of changebody, message and target
	private AdvChangeBodyList acbList = null;
	
	//element label list
	public static HashMap<String, Integer> elementLabelList = null;
	static
	{
		elementLabelList = new HashMap<String, Integer>();
		
		elementLabelList.put("change", CHANGE);
		elementLabelList.put("project", PROJECT);
		elementLabelList.put("commit_hash", COMMITHASH);
		elementLabelList.put("committer_date", COMMITTERDATE);
		elementLabelList.put("line", LINE);
		elementLabelList.put("target", TARGET);
	}
	
	//flag for readable characters
	boolean isReadable = false;
	
	boolean isMessage = false;
	boolean isTarget = false;
	
	class ChangeXMLParser extends DefaultHandler
	{
		//to record current commit hash
		private String commitHash = null;
		private String projName = null;
		private String commitDate = null;
		private String line = null;
		
		//object for changebody, message and target
		private AdvChangeBody acb = null;
		
		String elementContent = null;
		
		public ChangeXMLParser()
		{
	    	super();
	    	
			//instantiate cb, ml
			acb = new AdvChangeBody();
		}
		
	    public void startDocument()
	    {
	
	    }
	    
	    public void endDocument()
	    {
	    	//push remain items in buffer into db
	    	try
	    	{
	    		acbList.outsource();
	    	}
	    	catch(Exception e)
	    	{
	    		e.printStackTrace();
	    	}
	    }
	    
	    private String normalize(String str)
	    {
	    	if (str != null && !str.trim().equalsIgnoreCase("null") 
	    			&& !str.trim().equals(""))
	    	{
	    		str = str.trim();
	    		return StringEscapeUtils.escapeHtml(str);
	    		
	    		//return str;
	    	}
	    	
	    	return null;
	    }
	    
	    public void startElement (String uri, String name, 
	    		String qName, Attributes atts)
	    {	
	    	//get current element's label
	    	Integer currentElement = elementLabelList.get(name.trim());
	    	
	    	elementContent = null;
	   
	    	if (currentElement != null) 
	    			//&& currentElement >= CHANGE && currentElement <= TARGET) 
	    	{
	    		
				switch (currentElement) 
				{
					case CHANGE: 
					{
						//cb = new ChangeBody();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
						
						break;
					}
					case MESSAGE: 
					{			
						isMessage = true;
						
						//ml = new MultipleLines();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
											
						break;
					}
					case TARGET: 
					{
						isTarget = true;
						
						//ml = new MultipleLines();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
						
						break;
					}
					default: 
					{
						if ((currentElement >= PROJECT && currentElement <= LINE))
						{
							isReadable = true;
						
							//elementContent = new String();
						}
						
						break;
					}
				}
			}
	    	else
	    	{
	    		//set it to false, unreadable
	    		isReadable = false;
	    		
	    		elementContent = null;
	    	}
	    }
	    
	    public void endElement (String uri, String name, String qName)
	    {
	    	//get current element's label
	    	Integer currentElement = elementLabelList.get(name.trim());

	    	if(currentElement != null 
	    			&& currentElement >= CHANGE && currentElement <= TARGET)
	    	{
	    		
		    	switch(currentElement)
		    	{
		    		case CHANGE:
		    		{
		    			//add this changebody element to its list
		    			
		    			break;
		    		}
		    		case MESSAGE:
		    		{
		    			isMessage = false;
		 	
		    			break;
		    		}
		    		case TARGET:
		    		{
		    			isTarget = false;
		    			
		    			break;
		    		}
		    		case COMMITHASH:
		    		{
		    			//record current commit hash
		    			commitHash = elementContent;
		    			
		    			
		    			isReadable = true;
		    			
		    			break;
		    		}
		    		case COMMITTERDATE:
		    		{
		    			//record current commit hash
		    			commitDate = elementContent;
		    			
		    			isReadable = true;
		    			
		    			break;
		    		}
		    		case PROJECT:
		    		{
		    			//record current commit hash
		    			projName = elementContent;
		    			
		    			isReadable = true;
		    			
		    			
		    			break;
		    		}
		    		case LINE:
		    		{
		    			
		    			isReadable = true;

		    			if (isTarget)
		    			{
		    				//System.out.println("---------");
		    				
			    			//System.out.println(elementContent);
		    				
			    			acb.setElement(LINE, 
		    						elementContent);
			    			
		    				//add commit hash to cb
		    				acb.setElement(COMMITHASH, 
		    						commitHash);
		    				
		    				//add commit hash to cb
		    				acb.setElement(COMMITTERDATE, 
		    						commitDate);
		    				
		    				//add commit hash to cb
		    				acb.setElement(PROJECT, 
		    						projName);
		    				
			    			//add this changebody element to its list
			    			try
			    			{
								acbList.add(acb);
			    			}
			    			catch(Exception e)
			    			{
			    				e.printStackTrace();
			    			}
		    			}
		    			
		    			break;
		    		}
		    		default:
		    		{	
		    			if (isReadable)
		    			{
		    				
		    			}
		    			break;
		    		}    	
		    	}
		    	
	    	}
	    	
	    	//reset this flag to make following text unreadable
			isReadable = false;
			
			elementContent= null;
	    }
	    
	    public void characters (char ch[], int start, int length)
	    {
	    	//read in characters when current element is meaningful
	    	if (isReadable == true)
	    	{
	    		//System.out.println(new String(ch, start, length));
	    		
	    		//elementContent += normalize(new String(ch, start, length));
	    		
	    	
	    		if ((elementContent != null) || (length > 0))
	    		{	    		
		    		elementContent = (elementContent == null) 
		    				? normalize(new String(ch, start, length))
		    				: elementContent + normalize(new String(ch, start, length));
	    		}

	    	
	    		//elementContent = normalize(new String(ch, start, length));
	    		//System.out.println(elementContent);
	    	}
	    }
	}
	
	public AdvChangeReader(String filename) throws Exception
	{
		//pass in the filepath
		this.filename = filename;
		
		//instantiate an XML reader
		xr = XMLReaderFactory.createXMLReader();
		
		//set event handler and error handler
		ChangeXMLParser handler = new ChangeXMLParser();
		xr.setContentHandler(handler);
		xr.setErrorHandler(handler);
		
		//set up db
		db = new DB();
		
		try
		{
			db.connect();

			db.dropTable("advChangeBody");
			db.buildTable("advChangeBody");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
    	
		//set up changebody, message, target lists
    	acbList = new AdvChangeBodyList(db.getPreparedStatement("advChangeBody"));

	}
	
	private void parseXML()
	{
		try
		{
			xr.parse(filename);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public static void main(String args[]) throws Exception
	{
		DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
		Date date = new Date();
		//System.out.println(dateFormat.format(date));
		
		File dir = new File("data");
		String filepath = dir.getAbsolutePath() + "/git.log.xml";
		//String filepath = dir.getAbsolutePath() + "/view.xml";
		

		AdvChangeReader cr = new AdvChangeReader(filepath);

		
		cr.parseXML();
		
		db.close();
		
		date = new Date();
		System.out.println(dateFormat.format(date));

	}
}